import { Asset, JsonAsset, resources } from 'cc'

/**
 * 资源加载项定义，包含路径和类型
 */
export interface AssetLoadItem<T extends Asset = Asset> {
	/** 资源路径 */
	path: string
	/** 资源类型 */
	type: new () => T
}
/**
 * ZCC框架轻量级资源管理器
 * 对Cocos Creator资源管理系统的简单封装
 */
export class ZAssetManager {
	private static _instance: ZAssetManager = null

	// 资源缓存，用于跟踪已加载的资源
	private _loadedAssets: Map<string, Asset> = new Map()

	// 资源路径映射，用于从资源对象反查路径
	private _assetPaths: Map<string, string> = new Map()

	// 正在加载中的资源Promise，用于防止重复加载
	private _loadingPromises: Map<string, Promise<any>> = new Map()

	// 日志前缀
	private readonly LOG_TAG = '[ZAssetManager]'

	// 是否启用详细日志
	private _verboseLogging: boolean = false

	// 是否自动释放未使用的资源
	private _autoRelease: boolean = false

	// 资源引用计数
	private _assetRefCount: Map<string, number> = new Map()

	public static get instance(): ZAssetManager {
		if (!this._instance) {
			this._instance = new ZAssetManager()
		}
		return this._instance
	}

	/**
	 * 设置是否启用详细日志
	 */
	public setVerboseLogging(enabled: boolean): void {
		this._verboseLogging = enabled
	}

	/**
	 * 设置是否自动释放资源
	 * 启用后，当资源引用计数为0时自动释放
	 */
	public setAutoRelease(enabled: boolean): void {
		this._autoRelease = enabled
	}

	/**
	 * 加载资源（Promise风格API）
	 * 增加了缓存检查和重复加载防护
	 */
	public async load<T extends Asset>(path: string, type: new () => T): Promise<T> {
		// 1. 检查资源是否已加载
		if (this._loadedAssets.has(path)) {
			const cachedAsset = this._loadedAssets.get(path) as T

			// 确认类型匹配
			if (cachedAsset instanceof type) {
				this._log(`使用缓存资源: ${path}`)

				// 增加引用计数
				this._increaseRefCount(path)

				return cachedAsset
			} else {
				this._warn(`缓存资源类型不匹配: ${path}, 期望 ${type.name}, 实际 ${cachedAsset.constructor.name}`)
				// 类型不匹配，继续加载正确类型的资源
			}
		}

		// 2. 检查是否已有相同资源正在加载
		if (this._loadingPromises.has(path)) {
			this._log(`复用加载中的资源请求: ${path}`)
			return this._loadingPromises.get(path) as Promise<T>
		}

		// 3. 创建新的加载Promise
		const loadPromise = new Promise<T>((resolve, reject) => {
			this._log(`加载资源: ${path}`)

			resources.load(path, type, (err, asset: T) => {
				// 加载完成后，从加载中列表移除
				this._loadingPromises.delete(path)

				if (err) {
					this._error(`加载失败: ${path}`, err)
					reject(err)
				} else {
					this._log(`加载成功: ${path}`)

					// 记录已加载的资源和路径映射
					this._loadedAssets.set(path, asset)
					this._assetPaths.set(asset.uuid, path)

					// 初始化引用计数
					this._assetRefCount.set(path, 1)

					resolve(asset)
				}
			})
		})

		// 4. 将Promise添加到加载中列表
		this._loadingPromises.set(path, loadPromise)

		return loadPromise
	}

	/**
	 * 加载JSON并返回解析后的对象
	 */
	public async loadJson<T = any>(path: string): Promise<T> {
		try {
			const jsonAsset = await this.load(path, JsonAsset)

			if (!jsonAsset || !jsonAsset.json) {
				this._error(`JSON解析失败: ${path}，内容为空或格式不正确`)
				throw new Error('JSON内容为空或格式不正确')
			}

			return jsonAsset.json as T
		} catch (error) {
			throw error
		}
	}

	/**
	 * 增加资源引用计数
	 * @param path 资源路径
	 */
	private _increaseRefCount(path: string): void {
		const count = this._assetRefCount.get(path) || 0
		this._assetRefCount.set(path, count + 1)
		this._log(`资源引用计数增加: ${path}, 当前: ${count + 1}`)
	}

	/**
	 * 减少资源引用计数
	 * 当引用计数为0且启用自动释放时，自动释放资源
	 * @param path 资源路径
	 */
	private _decreaseRefCount(path: string): boolean {
		const count = this._assetRefCount.get(path) || 0

		if (count <= 0) {
			this._warn(`资源引用计数已为0: ${path}`)
			return false
		}

		const newCount = count - 1
		this._assetRefCount.set(path, newCount)
		this._log(`资源引用计数减少: ${path}, 当前: ${newCount}`)

		// 如果引用计数为0且启用了自动释放，则释放资源
		if (newCount === 0 && this._autoRelease) {
			this._releaseAsset(path)
			return true
		}

		return false
	}

	/**
	 * 内部释放资源方法
	 */
	private _releaseAsset(path: string): void {
		if (!this._loadedAssets.has(path)) {
			this._warn(`尝试释放未加载的资源: ${path}`)
			return
		}

		const asset = this._loadedAssets.get(path)

		try {
			this._log(`释放资源: ${path}`)
			resources.release(path)

			// 清理缓存
			this._loadedAssets.delete(path)
			this._assetPaths.delete(asset.uuid)
			this._assetRefCount.delete(path)
		} catch (error) {
			this._error(`释放资源失败: ${path}`, error)
		}
	}

	/**
	 * 释放资源
	 * @param pathOrAsset 资源路径或资源对象
	 * @param force 是否强制释放，忽略引用计数
	 */
	public release(pathOrAsset: string | Asset, force: boolean = false): void {
		let path: string

		if (typeof pathOrAsset === 'string') {
			// 如果传入的是路径
			path = pathOrAsset
		} else {
			// 如果传入的是资源对象，查找对应的路径
			path = this._assetPaths.get(pathOrAsset.uuid)
			if (!path) {
				this._warn(`无法找到资源的路径: ${pathOrAsset.name}`)
				return
			}
		}

		// 如果强制释放，直接释放资源
		if (force) {
			this._releaseAsset(path)
			return
		}

		// 否则减少引用计数，如果启用了自动释放且引用计数为0，会自动释放
		this._decreaseRefCount(path)
	}

	/**
	 * 释放所有已加载的资源
	 */
	public releaseAll(): void {
		this._log(`释放所有资源 (${this._loadedAssets.size} 个)`)

		// 遍历所有已加载的资源路径
		for (const [path, asset] of this._loadedAssets.entries()) {
			try {
				resources.release(path)
			} catch (error) {
				this._error(`释放资源失败: ${path}`, error)
			}
		}

		// 清理所有缓存
		this._loadedAssets.clear()
		this._assetPaths.clear()
		this._assetRefCount.clear()
		// 确保没有正在加载的资源
		this._loadingPromises.clear()
	}

	/**
	 * 检查资源是否已加载
	 * @param path 资源路径
	 */
	public isLoaded(path: string): boolean {
		return this._loadedAssets.has(path)
	}

	/**
	 * 检查资源是否正在加载
	 * @param path 资源路径
	 */
	public isLoading(path: string): boolean {
		return this._loadingPromises.has(path)
	}

	/**
	 * 获取已加载的资源
	 * @param path 资源路径
	 * @param autoRetain 是否自动增加引用计数
	 */
	public getAsset<T extends Asset>(path: string, autoRetain: boolean = true): T | null {
		const asset = (this._loadedAssets.get(path) as T) || null

		if (asset) {
			if (autoRetain) {
				this._increaseRefCount(path)
			}
		} else if (this._verboseLogging) {
			this._warn(`获取资源失败: ${path} (未加载)`)
		}

		return asset
	}

	/**
	 * 获取资源引用计数
	 * @param path 资源路径
	 */
	public getRefCount(path: string): number {
		return this._assetRefCount.get(path) || 0
	}

	/**
	 * 预加载同一类型的资源
	 * @param paths 资源路径数组
	 * @param type 资源类型
	 * @param onProgress 进度回调
	 * @param maxConcurrent 最大并发加载数，默认为5
	 */
	public async preload<T extends Asset>(
		paths: string[],
		type: new () => T,
		onProgress?: (finished: number, total: number, path: string) => void,
		maxConcurrent?: number
	): Promise<T[]>

	/**
	 * 预加载不同类型的资源
	 * @param items 资源加载项数组
	 * @param onProgress 进度回调
	 * @param maxConcurrent 最大并发加载数，默认为5
	 */
	public async preload(
		items: AssetLoadItem[],
		onProgress?: (finished: number, total: number, item: AssetLoadItem) => void,
		maxConcurrent?: number
	): Promise<Asset[]>

	/**
	 * 预加载资源实现
	 */
	public async preload<T extends Asset>(
		pathsOrItems: string[] | AssetLoadItem[],
		typeOrOnProgressOrMaxConcurrent?:
			| (new () => T)
			| ((finished: number, total: number, current: string | AssetLoadItem) => void)
			| number,
		onProgressOrMaxConcurrent?: ((finished: number, total: number, path: string) => void) | number,
		maxConcurrent: number = 5
	): Promise<Asset[]> {
		// 判断第一个参数是字符串数组还是加载项数组
		const isStringArray = typeof pathsOrItems[0] === 'string'

		// 处理参数
		let items: AssetLoadItem[]
		let progressCallback: ((finished: number, total: number, current: string | AssetLoadItem) => void) | undefined
		let actualMaxConcurrent = maxConcurrent

		if (isStringArray) {
			// 如果是字符串数组，则第二个参数必须是类型
			if (
				!typeOrOnProgressOrMaxConcurrent ||
				(typeof typeOrOnProgressOrMaxConcurrent === 'function' &&
					!(typeOrOnProgressOrMaxConcurrent as Function).prototype)
			) {
				throw new Error('当传入路径数组时，必须提供资源类型作为第二个参数')
			}

			// 转换为加载项数组
			items = (pathsOrItems as string[]).map(path => ({
				path,
				type: typeOrOnProgressOrMaxConcurrent as new () => T
			}))

			// 进度回调
			progressCallback = onProgressOrMaxConcurrent as
				| ((finished: number, total: number, path: string) => void)
				| undefined

			// 如果第三个参数是数字，则它是最大并发数
			if (typeof onProgressOrMaxConcurrent === 'number') {
				actualMaxConcurrent = onProgressOrMaxConcurrent
				progressCallback = undefined
			}
		} else {
			// 如果是加载项数组，则第二个参数是进度回调或最大并发数
			items = pathsOrItems as AssetLoadItem[]

			if (typeof typeOrOnProgressOrMaxConcurrent === 'function') {
				progressCallback = typeOrOnProgressOrMaxConcurrent as (
					finished: number,
					total: number,
					current: AssetLoadItem
				) => void
			} else if (typeof typeOrOnProgressOrMaxConcurrent === 'number') {
				actualMaxConcurrent = typeOrOnProgressOrMaxConcurrent
			}

			// 如果第三个参数是数字，则它是最大并发数
			if (typeof onProgressOrMaxConcurrent === 'number') {
				actualMaxConcurrent = onProgressOrMaxConcurrent
			}
		}

		const total = items.length
		const results: Asset[] = new Array(total)
		let finished = 0

		// 创建一个互斥锁，用于同步进度回调
		const progressLock = {
			locked: false,
			queue: [] as (() => void)[]
		}

		const acquireLock = async () => {
			if (progressLock.locked) {
				await new Promise<void>(resolve => {
					progressLock.queue.push(resolve)
				})
			}
			progressLock.locked = true
		}

		const releaseLock = () => {
			progressLock.locked = false
			if (progressLock.queue.length > 0) {
				const next = progressLock.queue.shift()
				next()
			}
		}

		// 并行加载资源
		const loadResource = async (index: number): Promise<void> => {
			if (index >= total) return

			const item = items[index]
			try {
				const asset = await this.load(item.path, item.type)
				results[index] = asset

				// 同步更新进度
				await acquireLock()
				try {
					finished++
					if (progressCallback) {
						progressCallback(finished, total, isStringArray ? (item.path as any) : item)
					}
				} finally {
					releaseLock()
				}

				// 加载下一个资源
				await loadResource(index + actualMaxConcurrent)
			} catch (error) {
				this._error(`预加载资源失败: ${item.path}`, error)

				// 同步更新进度
				await acquireLock()
				try {
					finished++
					if (progressCallback) {
						progressCallback(finished, total, isStringArray ? (item.path as any) : item)
					}
				} finally {
					releaseLock()
				}

				// 即使失败也继续加载下一个
				await loadResource(index + actualMaxConcurrent)
			}
		}

		// 启动初始的并发加载任务
		const initialTasks: Promise<void>[] = []
		for (let i = 0; i < Math.min(actualMaxConcurrent, total); i++) {
			initialTasks.push(loadResource(i))
		}

		// 等待所有加载任务完成
		await Promise.all(initialTasks)

		// 过滤掉加载失败的资源（结果数组中的undefined项）
		return results.filter(asset => asset !== undefined)
	}

	/**
	 * 获取当前已加载的资源数量
	 */
	public get loadedAssetCount(): number {
		return this._loadedAssets.size
	}

	/**
	 * 获取当前正在加载的资源数量
	 */
	public get loadingAssetCount(): number {
		return this._loadingPromises.size
	}

	/**
	 * 打印所有已加载的资源
	 */
	public printLoadedAssets(): void {
		console.log(`${this.LOG_TAG} 当前已加载资源 (${this._loadedAssets.size}):`)

		if (this._loadedAssets.size === 0) {
			console.log(`${this.LOG_TAG} 没有已加载的资源`)
			return
		}

		const assetList = Array.from(this._loadedAssets.entries()).map(([path, asset]) => {
			return {
				path,
				type: asset.constructor.name,
				refCount: this._assetRefCount.get(path) || 0
			}
		})

		console.table(assetList)
	}

	/**
	 * 记录普通日志
	 */
	private _log(message: string): void {
		if (this._verboseLogging) {
			console.log(`${this.LOG_TAG} ${message}`)
		}
	}

	/**
	 * 记录警告日志
	 */
	private _warn(message: string): void {
		console.warn(`${this.LOG_TAG} ${message}`)
	}

	/**
	 * 记录错误日志
	 */
	private _error(message: string, error?: any): void {
		if (error) {
			console.error(`${this.LOG_TAG} ${message}`, error)
		} else {
			console.error(`${this.LOG_TAG} ${message}`)
		}
	}
}
